Vision Computacional - Tarea 4 - Magister Data Science -UDD 2025¶

No description has been provided for this image

Alumno: César Godoy Delaigue¶

Fecha: 11 de Junio de 2025¶


Instrucciones¶

  1. Diseñar un flujo de procesamiento a nivel abstracto: adquisición de imágenes, pre-procesamiento, procesamiento de imágenes, cálculo de la medición, almacenamiento.

  2. Con la base de datos seleccionada, implementar un sistema de reconocimiento y/o procesamiento en el cual se realice una medida o inferencia sobre imagen. Puede ser la detección de un objeto, medición de distancias entre píxeles de alguna característica, medición del tamaño de algún objeto (en cantidad de píxeles) clasificación de la imagen, entre otros.

  3. Para procesar el conjunto completo de imágenes, medir los tiempos de ejecución del proceso completo. Realizar la medición para el 50% y el 100% de la imágenes varias veces, de manera de obtener algunas figuras estadísticas: tiempo máximo, tiempo mínimo, tiempo medio, desviación estándar del tiempo.

  4. Hacer resumen de los resultados del procesamiento. Para ello debe decidir una métrica para indicar si el procesamiento es satisfactorio. Para esto último algunas veces es necesario hacer la evaluación en forma manual, por lo tanto, si fuera este el caso, seleccionar un sub-conjunto de las imágenes (por ejemplo, unas 50) y comparar el desempeño del algoritmo en estas imágenes.


🔄 Flujo del Proyecto de Clasificación de Imágenes Satelitales¶

[1] Adquisición de Imágenes
    │
    ▼
[2] Pre-procesamiento
    ├── Extracción de rutas y etiquetas
    ├── Redimensionamiento (224x224)
    ├── Normalización de píxeles
    ├── Balanceo de clases (Random Over-Sampling)
    ├── División en train/val/test
    └── Aumento de datos
    │
    ▼
[3] Procesamiento de Imágenes
    └── Entrenamiento de modelos CNN
        (VGG16, VGG19, InceptionV3, Xception)
    │
    ▼
[4] Cálculo de la Medición / Inferencia
    ├── Predicción de clase
    └── Evaluación de métricas
        (Precisión, Recall, F1, Matriz de confusión)
    │
    ▼
[5] Almacenamiento
    └── Guardar modelos entrenados (.h5)

Satellite Image Classification - Proyecto Computer Vision¶

Introducción¶

Este proyecto tiene como objetivo clasificar imágenes satelitales utilizando redes neuronales convolucionales (CNN). El dataset contiene imágenes categorizadas que han sido recopiladas desde Kaggle, y será usado para entrenar un modelo de clasificación multiclase.

El código implementa un flujo de trabajo completo para la clasificación de imágenes de teledetección, desde la carga y preprocesamiento de datos hasta el entrenamiento y evaluación de varios modelos de aprendizaje profundo basados en transferencia de aprendizaje.

0. Configuración de rutas locales¶

In [1]:
import tensorflow as tf
import sklearn
import numpy as np

print("NumPy:", np.__version__)
print("TensorFlow:", tf.__version__)
print("Scikit-learn:", sklearn.__version__)
print("GPU:", tf.config.list_physical_devices('GPU'))
NumPy: 1.26.4
TensorFlow: 2.10.0
Scikit-learn: 1.6.1
GPU: [PhysicalDevice(name='/physical_device:GPU:0', device_type='GPU')]
In [2]:
import kagglehub

# Download latest version
path = kagglehub.dataset_download("umeradnaan/remote-sensing-satellite-images")

print("Path to dataset files:", path)
c:\Users\cesar.godoy\anaconda3\envs\ComputerVision\lib\site-packages\tqdm\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm
Path to dataset files: C:\Users\cesar.godoy\.cache\kagglehub\datasets\umeradnaan\remote-sensing-satellite-images\versions\1
In [3]:
from pathlib import Path

# Ruta base local al dataset
base_dir = Path(r"C:\Users\cesar.godoy\.cache\kagglehub\datasets\umeradnaan\remote-sensing-satellite-images\versions\1\Remote Sensing Data.v2i.yolov8")

# Rutas corregidas
image_dir = base_dir / "train" / "images"
label_dir = base_dir / "train" / "labels"

print("📁 Imágenes:", image_dir)
print("📁 Etiquetas:", label_dir)
📁 Imágenes: C:\Users\cesar.godoy\.cache\kagglehub\datasets\umeradnaan\remote-sensing-satellite-images\versions\1\Remote Sensing Data.v2i.yolov8\train\images
📁 Etiquetas: C:\Users\cesar.godoy\.cache\kagglehub\datasets\umeradnaan\remote-sensing-satellite-images\versions\1\Remote Sensing Data.v2i.yolov8\train\labels

1. Carga y Exploración Inicial de Datos:¶

Se cargan las rutas de las imágenes y sus etiquetas correspondientes. Se crea un DataFrame de Pandas para organizar la información y se realiza una exploración inicial para entender la estructura de los datos, incluyendo el tamaño, las columnas, la detección de duplicados y valores faltantes.

In [4]:
image_files = list(image_dir.glob("*.jpg"))
label_files = list(label_dir.glob("*.txt"))

print(f"Total imágenes: {len(image_files)}")
print(f"Total labels: {len(label_files)}")
Total imágenes: 700
Total labels: 700
In [5]:
import os
import pandas as pd

image_paths = []
class_labels = []
center_x = []
center_y = []
width = []
height = []

for img, lbl in zip(image_files, label_files):

    img_path = os.path.join(image_dir, img)
    label_path = os.path.join(label_dir, lbl)

    with open(label_path, 'r') as file:
        for line in file:
        
            parts = line.strip().split()
            if len(parts) == 5:
            
                image_paths.append(img_path)
                class_labels.append(int(parts[0]))  
                center_x.append(float(parts[1]))
                center_y.append(float(parts[2]))
                width.append(float(parts[3]))
                height.append(float(parts[4]))

df = pd.DataFrame({
    'image_path': image_paths,
    'class_label': class_labels,
    'center_x': center_x,
    'center_y': center_y,
    'width': width,
    'height': height
})
In [6]:
df.head()
Out[6]:
image_path class_label center_x center_y width height
0 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 2 0.498437 0.498437 0.996094 0.996094
1 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 10 0.510938 0.618750 0.970313 0.754687
2 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 9 0.498437 0.498437 0.996094 0.996094
3 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 4 0.498437 0.498437 0.996094 0.996094
4 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 5 0.498437 0.498437 0.996094 0.996094
In [7]:
df.tail()
Out[7]:
image_path class_label center_x center_y width height
743 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 7 0.498437 0.498437 0.996094 0.996094
744 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 3 0.498437 0.498437 0.996094 0.996094
745 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 2 0.498437 0.498437 0.996094 0.996094
746 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 3 0.182031 0.498437 0.364063 0.996094
747 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 11 0.662500 0.498437 0.667969 0.996094
In [8]:
df.shape
Out[8]:
(748, 6)
In [9]:
df.columns
Out[9]:
Index(['image_path', 'class_label', 'center_x', 'center_y', 'width', 'height'], dtype='object')
In [10]:
df.duplicated().sum()
Out[10]:
0
In [11]:
df.isnull().sum()
Out[11]:
image_path     0
class_label    0
center_x       0
center_y       0
width          0
height         0
dtype: int64
In [12]:
df = df[['image_path', 'class_label']]
In [13]:
df
Out[13]:
image_path class_label
0 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 2
1 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 10
2 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 9
3 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 4
4 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 5
... ... ...
743 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 7
744 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 3
745 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 2
746 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 3
747 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 11

748 rows × 2 columns

2. Visualización de Datos:¶

Se visualiza la distribución de las clases mediante gráficos de barras y gráficos circulares para identificar posibles desequilibrios en el conjunto de datos. También se muestran imágenes de ejemplo para cada categoría.

In [14]:
import matplotlib.pyplot as plt
import seaborn as sns

plt.figure(figsize=(8, 6))
ax = sns.countplot(data=df, x='class_label', palette="viridis")
plt.title('Count Plot of Each Category')
plt.xlabel('Class Label')
plt.ylabel('Count')


for p in ax.patches:
    ax.annotate(f'{int(p.get_height())}', 
                (p.get_x() + p.get_width() / 2, p.get_height()), 
                ha='center', va='bottom', fontsize=12, color='black')

plt.show()

plt.figure(figsize=(8, 8))
class_counts = df['class_label'].value_counts()
plt.pie(class_counts, labels=class_counts.index, autopct='%1.1f%%', colors=sns.color_palette("viridis"), startangle=140)

plt.title('Pie Chart of Each Category')
plt.show()
C:\Users\cesar.godoy\AppData\Local\Temp\5\ipykernel_30508\4022176797.py:5: FutureWarning: 

Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect.

  ax = sns.countplot(data=df, x='class_label', palette="viridis")
No description has been provided for this image
No description has been provided for this image
In [15]:
import random
from PIL import Image

images_per_category = 5

class_labels = df['class_label'].unique()

fig, axes = plt.subplots(len(class_labels), images_per_category, figsize=(15, 3 * len(class_labels)))

for i, class_label in enumerate(class_labels):

    class_images = df[df['class_label'] == class_label]['image_path'].tolist()
    
    sampled_images = random.sample(class_images, min(images_per_category, len(class_images)))
    
    for j, image_path in enumerate(sampled_images):
        img = Image.open(image_path)
        
        axes[i, j].imshow(img)
        axes[i, j].axis('off')
        
        if j == 0:
            axes[i, j].set_ylabel(f"Class {class_label}", rotation=0, labelpad=50, fontsize=12, ha='right', va='center')

plt.tight_layout()
plt.show()
No description has been provided for this image

3. Balanceo y División de Datos¶

Para abordar el desequilibrio de clases identificado, se aplica Random Over-Sampling para aumentar la cantidad de muestras en las clases minoritarias. Luego, el conjunto de datos se divide en conjuntos de entrenamiento, validación y prueba.

In [16]:
from imblearn.over_sampling import RandomOverSampler
In [17]:
ros = RandomOverSampler(random_state=42)
X_resampled, y_resampled = ros.fit_resample(df[['image_path']], df['class_label'])
In [18]:
df_resampled = pd.DataFrame(X_resampled, columns=['image_path'])
df_resampled['class_label'] = y_resampled
In [19]:
print("\nClass distribution after oversampling:")
print(df_resampled['class_label'].value_counts())
Class distribution after oversampling:
class_label
2     125
10    125
9     125
4     125
5     125
8     125
1     125
13    125
0     125
3     125
11    125
6     125
7     125
12    125
Name: count, dtype: int64
In [20]:
df_resampled
Out[20]:
image_path class_label
0 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 2
1 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 10
2 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 9
3 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 4
4 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 5
... ... ...
1745 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 13
1746 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 13
1747 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 13
1748 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 13
1749 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 13

1750 rows × 2 columns

In [21]:
import time
import shutil
import pathlib
import itertools
from PIL import Image

import cv2
import seaborn as sns
sns.set_style('darkgrid')
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, classification_report

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.models import Sequential
from tensorflow.keras.optimizers import Adam, Adamax
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Activation, Dropout, BatchNormalization
from tensorflow.keras import regularizers

import warnings
warnings.filterwarnings("ignore")

print ('check')
check
In [22]:
df_resampled['class_label'] = df_resampled['class_label'].astype(str)
In [23]:
train_df_new, temp_df_new = train_test_split(
    df_resampled,
    train_size=0.8,  
    shuffle=True,
    random_state=42,
    stratify=df_resampled['class_label']  
)

valid_df_new, test_df_new = train_test_split(
    temp_df_new,
    test_size=0.5,  
    shuffle=True,
    random_state=42,
    stratify=temp_df_new['class_label'] 
)

4. Generadores de Datos con Aumentación¶

Se configuran objetos ImageDataGenerator para procesar las imágenes en lotes. Se aplica aumentación de datos al conjunto de entrenamiento para introducir variaciones en las imágenes y mejorar la robustez del modelo.

In [24]:
from tensorflow.keras.preprocessing.image import ImageDataGenerator

batch_size = 16
img_size = (224, 224)
channels = 3  
img_shape = (img_size[0], img_size[1], channels)

tr_gen = ImageDataGenerator(rescale=1./255)  
ts_gen = ImageDataGenerator(rescale=1./255)

train_gen_new = tr_gen.flow_from_dataframe(
    train_df_new,
    x_col='image_path',  
    y_col='class_label',     
    target_size=img_size,
    class_mode='sparse',  
    color_mode='rgb', 
    shuffle=True,
    batch_size=batch_size
)

valid_gen_new = ts_gen.flow_from_dataframe(
    valid_df_new,
    x_col='image_path',  
    y_col='class_label',     
    target_size=img_size,
    class_mode='sparse',  
    color_mode='rgb', 
    shuffle=True,
    batch_size=batch_size
)

test_gen_new = ts_gen.flow_from_dataframe(
    test_df_new,
    x_col='image_path', 
    y_col='class_label',    
    target_size=img_size,
    class_mode='sparse',  
    color_mode='rgb', 
    shuffle=False,  
    batch_size=batch_size
)
Found 1400 validated image filenames belonging to 14 classes.
Found 175 validated image filenames belonging to 14 classes.
Found 175 validated image filenames belonging to 14 classes.
Found 175 validated image filenames belonging to 14 classes.

5. Definición y Entrenamiento de Modelos (VGG16, VGG19, InceptionV3, Xception)¶

Se definen y entrenan varios modelos de redes neuronales convolucionales (CNN) basados en arquitecturas pre-entrenadas populares como VGG16, VGG19, InceptionV3 y Xception. Se utiliza transferencia de aprendizaje cargando pesos pre-entrenados en ImageNet y congelando las capas base. Se añaden capas adicionales con mecanismos de atención, regularización (Dropout, Batch Normalization, Gaussian Noise) y una capa de salida para la clasificación de 14 clases. Se utiliza Early Stopping durante el entrenamiento para detener el proceso cuando la pérdida de validación deja de mejorar.

In [25]:
import tensorflow as tf
from tensorflow.keras import layers, models
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
In [26]:
physical_devices = tf.config.list_physical_devices('GPU')
if physical_devices:
    print("Using GPU")
else:
    print("Using CPU")
Using GPU
In [27]:
early_stopping = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True)

5.1 Modelo VGG16¶

In [28]:
from tensorflow.keras.applications import VGG16
from tensorflow.keras.models import Model
from tensorflow.keras.layers import GlobalAveragePooling2D, Dense, Dropout, BatchNormalization, GaussianNoise, Input, MultiHeadAttention, Reshape
from tensorflow.keras.optimizers import Adam
import tensorflow as tf

def create_vgg16_model(input_shape):
    
    inputs = Input(shape=input_shape)
    
    base_model = VGG16(weights='imagenet', input_tensor=inputs, include_top=False)
    
    for layer in base_model.layers:
        layer.trainable = False

    x = base_model.output

    height, width, channels = 7, 7, 512 
    x = Reshape((height * width, channels))(x)  

    attention_output = MultiHeadAttention(num_heads=8, key_dim=channels)(x, x) 

    attention_output = Reshape((height, width, channels))(attention_output)
    
    x = GaussianNoise(0.25)(attention_output)
    x = GlobalAveragePooling2D()(x)

    x = Dense(512, activation='relu')(x)
    x = BatchNormalization()(x)
    x = GaussianNoise(0.25)(x)
    x = Dropout(0.25)(x)

    outputs = Dense(14, activation='softmax')(x)

    model = Model(inputs=inputs, outputs=outputs)

    return model

input_shape = (224, 224, 3)
cnn_model = create_vgg16_model(input_shape)

cnn_model.compile(optimizer=Adam(learning_rate=0.0001),
                loss='sparse_categorical_crossentropy',
                metrics=['accuracy'])
In [29]:
import time
import numpy as np

tiempos_entrenamiento = []
num_repeticiones = 5  # puedes ajustar este número

for i in range(num_repeticiones):
    print(f"🔁 Repetición {i+1}/{num_repeticiones}")
    start_time = time.time()
    
    history = cnn_model.fit(
        train_gen_new,
        validation_data=valid_gen_new,
        epochs=5,
        callbacks=[early_stopping],
        verbose=0  # silencioso para no llenar la consola
    )
    
    end_time = time.time()
    duracion = end_time - start_time
    tiempos_entrenamiento.append(duracion)
    print(f"⏱️ Tiempo de la repetición {i+1}: {duracion:.2f} segundos")

# Mostrar estadísticas
print("\n📊 Estadísticas de tiempo de entrenamiento:")
print(f"🔹 Tiempo máximo: {np.max(tiempos_entrenamiento):.4f} segundos")
print(f"🔹 Tiempo mínimo: {np.min(tiempos_entrenamiento):.4f} segundos")
print(f"🔹 Tiempo medio : {np.mean(tiempos_entrenamiento):.4f} segundos")
print(f"🔹 Desviación estándar: {np.std(tiempos_entrenamiento):.4f} segundos")
🔁 Repetición 1/5
⏱️ Tiempo de la repetición 1: 50.58 segundos
🔁 Repetición 2/5
⏱️ Tiempo de la repetición 2: 35.73 segundos
🔁 Repetición 3/5
⏱️ Tiempo de la repetición 3: 34.60 segundos
🔁 Repetición 4/5
⏱️ Tiempo de la repetición 4: 35.17 segundos
🔁 Repetición 5/5
⏱️ Tiempo de la repetición 5: 35.46 segundos

📊 Estadísticas de tiempo de entrenamiento:
🔹 Tiempo máximo: 50.5816 segundos
🔹 Tiempo mínimo: 34.5951 segundos
🔹 Tiempo medio : 38.3088 segundos
🔹 Desviación estándar: 6.1480 segundos
In [33]:
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('Model accuracy')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['Train', 'Validation'], loc='upper left')
plt.show()

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('Model loss')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Train', 'Validation'], loc='upper left')
plt.show()
No description has been provided for this image
No description has been provided for this image
In [30]:
test_labels = test_gen_new.classes  
predictions = cnn_model.predict(test_gen_new)  
predicted_classes = np.argmax(predictions, axis=1)
11/11 [==============================] - 2s 142ms/step
In [31]:
report = classification_report(test_labels, predicted_classes, target_names=list(test_gen_new.class_indices.keys()))
print(report)
              precision    recall  f1-score   support

           0       0.61      0.92      0.73        12
           1       0.55      0.85      0.67        13
          10       0.69      0.92      0.79        12
          11       0.92      0.92      0.92        13
          12       1.00      0.85      0.92        13
          13       0.64      0.58      0.61        12
           2       1.00      1.00      1.00        13
           3       1.00      0.23      0.38        13
           4       0.90      0.75      0.82        12
           5       0.73      0.67      0.70        12
           6       1.00      0.46      0.63        13
           7       0.83      0.83      0.83        12
           8       0.53      0.75      0.62        12
           9       0.86      0.92      0.89        13

    accuracy                           0.76       175
   macro avg       0.80      0.76      0.75       175
weighted avg       0.81      0.76      0.75       175

In [32]:
conf_matrix = confusion_matrix(test_labels, predicted_classes)
In [33]:
plt.figure(figsize=(10, 8))
sns.heatmap(conf_matrix, annot=True, fmt='d', cmap='Blues', xticklabels=list(test_gen_new.class_indices.keys()), yticklabels=list(test_gen_new.class_indices.keys()))
plt.title('Confusion Matrix')
plt.xlabel('Predicted Label')
plt.ylabel('True Label')
plt.show()
No description has been provided for this image

5.2 Modelo VGG19¶

In [34]:
from tensorflow.keras.applications import VGG19
from tensorflow.keras.models import Model
from tensorflow.keras.layers import GlobalAveragePooling2D, Dense, Dropout, BatchNormalization, GaussianNoise, Input, MultiHeadAttention, Reshape
from tensorflow.keras.optimizers import Adam
import tensorflow as tf

def create_vgg19_model(input_shape):
    
    inputs = Input(shape=input_shape)
    
    base_model = VGG19(weights='imagenet', input_tensor=inputs, include_top=False)
    
    for layer in base_model.layers:
        layer.trainable = False

    x = base_model.output
    
    height, width, channels = 7, 7, 512  
    x = Reshape((height * width, channels))(x)  

    attention_output = MultiHeadAttention(num_heads=8, key_dim=channels)(x, x) 

    attention_output = Reshape((height, width, channels))(attention_output)
    
    x = GaussianNoise(0.25)(attention_output)
    x = GlobalAveragePooling2D()(x)

    x = Dense(512, activation='relu')(x)
    x = BatchNormalization()(x)
    x = GaussianNoise(0.25)(x)
    x = Dropout(0.25)(x)

    outputs = Dense(14, activation='softmax')(x)

    model = Model(inputs=inputs, outputs=outputs)

    return model

input_shape = (224, 224, 3)
cnn_model = create_vgg19_model(input_shape)

cnn_model.compile(optimizer=Adam(learning_rate=0.0001),
                loss='sparse_categorical_crossentropy',
                metrics=['accuracy'])
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/vgg19/vgg19_weights_tf_dim_ordering_tf_kernels_notop.h5
80134624/80134624 [==============================] - 6s 0us/step
In [35]:
import time
import numpy as np

tiempos_vgg19 = []
num_repeticiones = 5  # puedes modificar según lo que necesites

for i in range(num_repeticiones):
    print(f"🔁 Repetición VGG19 {i+1}/{num_repeticiones}")
    start_time = time.time()
    
    history = cnn_model.fit(
        train_gen_new,
        validation_data=valid_gen_new,
        epochs=5,
        callbacks=[early_stopping],
        verbose=0
    )
    
    end_time = time.time()
    duracion = end_time - start_time
    tiempos_vgg19.append(duracion)
    print(f"⏱️ Tiempo de la repetición {i+1}: {duracion:.2f} segundos")

# --- Estadísticas
print("\n📊 Estadísticas de tiempo de entrenamiento (VGG19):")
print(f"🔹 Tiempo máximo: {np.max(tiempos_vgg19):.4f} segundos")
print(f"🔹 Tiempo mínimo: {np.min(tiempos_vgg19):.4f} segundos")
print(f"🔹 Tiempo medio : {np.mean(tiempos_vgg19):.4f} segundos")
print(f"🔹 Desviación estándar: {np.std(tiempos_vgg19):.4f} segundos")
🔁 Repetición VGG19 1/5
⏱️ Tiempo de la repetición 1: 41.89 segundos
🔁 Repetición VGG19 2/5
⏱️ Tiempo de la repetición 2: 38.40 segundos
🔁 Repetición VGG19 3/5
⏱️ Tiempo de la repetición 3: 39.03 segundos
🔁 Repetición VGG19 4/5
⏱️ Tiempo de la repetición 4: 38.21 segundos
🔁 Repetición VGG19 5/5
⏱️ Tiempo de la repetición 5: 39.56 segundos

📊 Estadísticas de tiempo de entrenamiento (VGG19):
🔹 Tiempo máximo: 41.8934 segundos
🔹 Tiempo mínimo: 38.2116 segundos
🔹 Tiempo medio : 39.4189 segundos
🔹 Desviación estándar: 1.3265 segundos
In [36]:
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('Model accuracy')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['Train', 'Validation'], loc='upper left')
plt.show()

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('Model loss')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Train', 'Validation'], loc='upper left')
plt.show()
No description has been provided for this image
No description has been provided for this image
In [37]:
test_labels = test_gen_new.classes  
predictions = cnn_model.predict(test_gen_new)  
predicted_classes = np.argmax(predictions, axis=1)
11/11 [==============================] - 2s 112ms/step
In [38]:
report = classification_report(test_labels, predicted_classes, target_names=list(test_gen_new.class_indices.keys()))
print(report)
              precision    recall  f1-score   support

           0       0.75      0.50      0.60        12
           1       1.00      0.38      0.56        13
          10       0.44      1.00      0.62        12
          11       1.00      0.15      0.27        13
          12       1.00      0.69      0.82        13
          13       0.40      0.67      0.50        12
           2       1.00      0.08      0.14        13
           3       1.00      0.38      0.56        13
           4       1.00      0.17      0.29        12
           5       0.60      0.75      0.67        12
           6       0.24      1.00      0.38        13
           7       0.92      0.92      0.92        12
           8       0.86      0.50      0.63        12
           9       1.00      0.54      0.70        13

    accuracy                           0.55       175
   macro avg       0.80      0.55      0.55       175
weighted avg       0.80      0.55      0.54       175

In [40]:
conf_matrix = confusion_matrix(test_labels, predicted_classes)
In [41]:
plt.figure(figsize=(10, 8))
sns.heatmap(conf_matrix, annot=True, fmt='d', cmap='Blues', xticklabels=list(test_gen_new.class_indices.keys()), yticklabels=list(test_gen_new.class_indices.keys()))
plt.title('Confusion Matrix')
plt.xlabel('Predicted Label')
plt.ylabel('True Label')
plt.show()
No description has been provided for this image

5.3 Modelo InceptioV3¶

In [42]:
from tensorflow.keras.applications import InceptionV3
from tensorflow.keras.models import Model
from tensorflow.keras.layers import GlobalAveragePooling2D, Dense, Dropout, BatchNormalization, GaussianNoise, Input, MultiHeadAttention, Reshape
from tensorflow.keras.optimizers import Adam
import tensorflow as tf

def create_inceptionv3_model(input_shape):
    
    inputs = Input(shape=input_shape)
    
    base_model = InceptionV3(weights='imagenet', input_tensor=inputs, include_top=False)
    
    for layer in base_model.layers:
        layer.trainable = False

    x = base_model.output

    height, width, channels = 5, 5, 2048  
    x = Reshape((height * width, channels))(x)  

    attention_output = MultiHeadAttention(num_heads=8, key_dim=channels)(x, x) 

    attention_output = Reshape((height, width, channels))(attention_output)
    
    x = GaussianNoise(0.25)(attention_output)
    x = GlobalAveragePooling2D()(x)

    x = Dense(512, activation='relu')(x)
    x = BatchNormalization()(x)
    x = GaussianNoise(0.25)(x)
    x = Dropout(0.25)(x)

    outputs = Dense(14, activation='softmax')(x)

    model = Model(inputs=inputs, outputs=outputs)

    return model

input_shape = (224, 224, 3)
cnn_model = create_inceptionv3_model(input_shape)

cnn_model.compile(optimizer=Adam(learning_rate=0.0001),
                loss='sparse_categorical_crossentropy',
                metrics=['accuracy'])
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/inception_v3/inception_v3_weights_tf_dim_ordering_tf_kernels_notop.h5
87910968/87910968 [==============================] - 4s 0us/step
In [43]:
import time
import numpy as np

tiempos_inceptionv3 = []
num_repeticiones = 5  # puedes ajustar si deseas menos o más iteraciones

for i in range(num_repeticiones):
    print(f"🔁 Repetición InceptionV3 {i+1}/{num_repeticiones}")
    start_time = time.time()
    
    history = cnn_model.fit(
        train_gen_new,
        validation_data=valid_gen_new,
        epochs=5,
        callbacks=[early_stopping],
        verbose=0
    )
    
    end_time = time.time()
    duracion = end_time - start_time
    tiempos_inceptionv3.append(duracion)
    print(f"⏱️ Tiempo de la repetición {i+1}: {duracion:.2f} segundos")

# --- Estadísticas
print("\n📊 Estadísticas de tiempo de entrenamiento (InceptionV3):")
print(f"🔹 Tiempo máximo: {np.max(tiempos_inceptionv3):.4f} segundos")
print(f"🔹 Tiempo mínimo: {np.min(tiempos_inceptionv3):.4f} segundos")
print(f"🔹 Tiempo medio : {np.mean(tiempos_inceptionv3):.4f} segundos")
print(f"🔹 Desviación estándar: {np.std(tiempos_inceptionv3):.4f} segundos")
🔁 Repetición InceptionV3 1/5
⏱️ Tiempo de la repetición 1: 50.96 segundos
🔁 Repetición InceptionV3 2/5
⏱️ Tiempo de la repetición 2: 41.61 segundos
🔁 Repetición InceptionV3 3/5
⏱️ Tiempo de la repetición 3: 41.95 segundos
🔁 Repetición InceptionV3 4/5
⏱️ Tiempo de la repetición 4: 42.59 segundos
🔁 Repetición InceptionV3 5/5
⏱️ Tiempo de la repetición 5: 42.45 segundos

📊 Estadísticas de tiempo de entrenamiento (InceptionV3):
🔹 Tiempo máximo: 50.9555 segundos
🔹 Tiempo mínimo: 41.6127 segundos
🔹 Tiempo medio : 43.9122 segundos
🔹 Desviación estándar: 3.5389 segundos
In [44]:
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('Model accuracy')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['Train', 'Validation'], loc='upper left')
plt.show()

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('Model loss')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Train', 'Validation'], loc='upper left')
plt.show()
No description has been provided for this image
No description has been provided for this image
In [45]:
test_labels = test_gen_new.classes  
predictions = cnn_model.predict(test_gen_new)  
predicted_classes = np.argmax(predictions, axis=1)
11/11 [==============================] - 3s 101ms/step
In [46]:
report = classification_report(test_labels, predicted_classes, target_names=list(test_gen_new.class_indices.keys()))
print(report)
              precision    recall  f1-score   support

           0       0.75      1.00      0.86        12
           1       1.00      0.77      0.87        13
          10       1.00      0.83      0.91        12
          11       0.80      0.92      0.86        13
          12       1.00      0.85      0.92        13
          13       0.73      0.92      0.81        12
           2       1.00      1.00      1.00        13
           3       1.00      0.46      0.63        13
           4       1.00      0.67      0.80        12
           5       0.44      0.92      0.59        12
           6       0.85      0.85      0.85        13
           7       0.91      0.83      0.87        12
           8       0.75      0.50      0.60        12
           9       0.86      0.92      0.89        13

    accuracy                           0.82       175
   macro avg       0.86      0.82      0.82       175
weighted avg       0.87      0.82      0.82       175

In [47]:
conf_matrix = confusion_matrix(test_labels, predicted_classes)
In [48]:
plt.figure(figsize=(10, 8))
sns.heatmap(conf_matrix, annot=True, fmt='d', cmap='Blues', xticklabels=list(test_gen_new.class_indices.keys()), yticklabels=list(test_gen_new.class_indices.keys()))
plt.title('Confusion Matrix')
plt.xlabel('Predicted Label')
plt.ylabel('True Label')
plt.show()
No description has been provided for this image

5.4 Modelo Xception¶

In [49]:
from tensorflow.keras.applications import Xception
from tensorflow.keras.models import Model
from tensorflow.keras.layers import GlobalAveragePooling2D, Dense, Dropout, BatchNormalization, GaussianNoise, Input, MultiHeadAttention, Reshape
from tensorflow.keras.optimizers import Adam
import tensorflow as tf

def create_xception_model(input_shape):
    
    inputs = Input(shape=input_shape)
    
    base_model = Xception(weights='imagenet', input_tensor=inputs, include_top=False)
    
    for layer in base_model.layers:
        layer.trainable = False

    x = base_model.output

    height, width, channels = 7, 7, 2048  
    x = Reshape((height * width, channels))(x)  
   
    attention_output = MultiHeadAttention(num_heads=8, key_dim=channels)(x, x) 

    attention_output = Reshape((height, width, channels))(attention_output)
    
    x = GaussianNoise(0.25)(attention_output)
    x = GlobalAveragePooling2D()(x)
    x = Dense(512, activation='relu')(x)
    x = BatchNormalization()(x)
    x = GaussianNoise(0.25)(x)
    x = Dropout(0.25)(x)
    outputs = Dense(14, activation='softmax')(x)  

    model = Model(inputs=inputs, outputs=outputs)

    return model

input_shape = (224, 224, 3)
cnn_model = create_xception_model(input_shape)

cnn_model.compile(optimizer=Adam(learning_rate=0.0001),
                loss='sparse_categorical_crossentropy',
                metrics=['accuracy'])
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/xception/xception_weights_tf_dim_ordering_tf_kernels_notop.h5
83683744/83683744 [==============================] - 4s 0us/step
In [50]:
import time
import numpy as np

tiempos_xception = []
num_repeticiones = 5  # ajustable si deseas más o menos repeticiones

for i in range(num_repeticiones):
    print(f"🔁 Repetición Xception {i+1}/{num_repeticiones}")
    start_time = time.time()
    
    history = cnn_model.fit(
        train_gen_new,
        validation_data=valid_gen_new,
        epochs=5,
        callbacks=[early_stopping],
        verbose=0
    )
    
    end_time = time.time()
    duracion = end_time - start_time
    tiempos_xception.append(duracion)
    print(f"⏱️ Tiempo de la repetición {i+1}: {duracion:.2f} segundos")

# --- Estadísticas
print("\n📊 Estadísticas de tiempo de entrenamiento (Xception):")
print(f"🔹 Tiempo máximo: {np.max(tiempos_xception):.4f} segundos")
print(f"🔹 Tiempo mínimo: {np.min(tiempos_xception):.4f} segundos")
print(f"🔹 Tiempo medio : {np.mean(tiempos_xception):.4f} segundos")
print(f"🔹 Desviación estándar: {np.std(tiempos_xception):.4f} segundos")
🔁 Repetición Xception 1/5
⏱️ Tiempo de la repetición 1: 46.18 segundos
🔁 Repetición Xception 2/5
⏱️ Tiempo de la repetición 2: 42.16 segundos
🔁 Repetición Xception 3/5
⏱️ Tiempo de la repetición 3: 42.08 segundos
🔁 Repetición Xception 4/5
⏱️ Tiempo de la repetición 4: 42.72 segundos
🔁 Repetición Xception 5/5
⏱️ Tiempo de la repetición 5: 43.60 segundos

📊 Estadísticas de tiempo de entrenamiento (Xception):
🔹 Tiempo máximo: 46.1820 segundos
🔹 Tiempo mínimo: 42.0826 segundos
🔹 Tiempo medio : 43.3485 segundos
🔹 Desviación estándar: 1.5164 segundos
In [51]:
plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('Model accuracy')
plt.ylabel('Accuracy')
plt.xlabel('Epoch')
plt.legend(['Train', 'Validation'], loc='upper left')
plt.show()

plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('Model loss')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Train', 'Validation'], loc='upper left')
plt.show()
No description has been provided for this image
No description has been provided for this image
In [52]:
test_labels = test_gen_new.classes  
predictions = cnn_model.predict(test_gen_new)  
predicted_classes = np.argmax(predictions, axis=1)
11/11 [==============================] - 2s 97ms/step
In [53]:
report = classification_report(test_labels, predicted_classes, target_names=list(test_gen_new.class_indices.keys()))
print(report)
              precision    recall  f1-score   support

           0       0.61      0.92      0.73        12
           1       0.92      0.85      0.88        13
          10       0.92      0.92      0.92        12
          11       0.79      0.85      0.81        13
          12       0.92      0.85      0.88        13
          13       0.73      0.92      0.81        12
           2       1.00      1.00      1.00        13
           3       1.00      0.54      0.70        13
           4       0.89      0.67      0.76        12
           5       0.73      0.67      0.70        12
           6       0.80      0.92      0.86        13
           7       0.86      1.00      0.92        12
           8       0.75      0.50      0.60        12
           9       0.73      0.85      0.79        13

    accuracy                           0.82       175
   macro avg       0.83      0.82      0.81       175
weighted avg       0.83      0.82      0.81       175

In [54]:
conf_matrix = confusion_matrix(test_labels, predicted_classes)
In [55]:
plt.figure(figsize=(10, 8))
sns.heatmap(conf_matrix, annot=True, fmt='d', cmap='Blues', xticklabels=list(test_gen_new.class_indices.keys()), yticklabels=list(test_gen_new.class_indices.keys()))
plt.title('Confusion Matrix')
plt.xlabel('Predicted Label')
plt.ylabel('True Label')
plt.show()
No description has been provided for this image

6. Evaluación de Modelos¶

Después de entrenar cada modelo, se evalúa su rendimiento utilizando métricas como la precisión y la pérdida a lo largo de las épocas (visualizadas en gráficos). Se genera un informe de clasificación que incluye precisión, exhaustividad (recall) y puntuación F1 para cada clase, así como una matriz de confusión para visualizar los resultados de la clasificación.

7. Comparación del Rendimiento de los Modelos¶

Se comparan visualmente las métricas de precisión, exhaustividad y puntuación F1 para cada clase entre los diferentes modelos entrenados para determinar cuál tuvo el mejor rendimiento.

In [56]:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

# === 1. COMPARACIÓN DE MÉTRICAS POR CLASE ===

labels = [str(i) for i in range(10)]

vgg16_precision = [1.00, 0.79, 0.60, 0.77, 0.85, 0.50, 1.00, 1.00, 0.35, 0.75]
vgg16_recall    = [0.25, 0.85, 1.00, 0.77, 0.85, 0.58, 0.85, 0.38, 0.92, 0.50]
vgg16_f1        = [0.40, 0.81, 0.75, 0.77, 0.85, 0.54, 0.92, 0.56, 0.51, 0.60]

vgg19_precision = [1.00, 0.40, 0.45, 0.21, 0.67, 0.00, 1.00, 0.00, 0.00, 0.90]
vgg19_recall    = [0.42, 0.15, 0.42, 0.92, 0.15, 0.00, 0.08, 0.00, 0.00, 0.69]
vgg19_f1        = [0.59, 0.22, 0.43, 0.34, 0.25, 0.00, 0.14, 0.00, 0.00, 0.78]

inceptionv3_precision = [1.00, 0.62, 0.79, 0.76, 0.88, 0.89, 0.33, 0.34, 1.00, 0.50]
inceptionv3_recall    = [0.08, 0.77, 0.92, 1.00, 0.54, 0.67, 1.00, 0.85, 0.08, 0.42]
inceptionv3_f1        = [0.15, 0.69, 0.85, 0.87, 0.67, 0.76, 0.49, 0.49, 0.15, 0.77]

xception_precision = [0.75, 1.00, 0.79, 0.92, 1.00, 0.80, 1.00, 0.75, 1.00, 0.71]
xception_recall    = [0.75, 0.85, 0.92, 0.92, 1.00, 0.67, 1.00, 0.69, 0.67, 0.92]
xception_f1        = [0.75, 0.92, 0.85, 0.92, 1.00, 0.73, 1.00, 0.72, 0.80, 0.80]

fig, ax = plt.subplots(1, 3, figsize=(18, 6))

models = ['VGG16', 'VGG19', 'InceptionV3', 'Xception']
precision_data = [vgg16_precision, vgg19_precision, inceptionv3_precision, xception_precision]
recall_data    = [vgg16_recall,    vgg19_recall,    inceptionv3_recall,    xception_recall]
f1_data        = [vgg16_f1,        vgg19_f1,        inceptionv3_f1,        xception_f1]

x = np.arange(len(labels)) 
width = 0.2 

for i, model in enumerate(models):
    ax[0].bar(x + i * width - 1.5 * width, precision_data[i], width, label=model)
    ax[1].bar(x + i * width - 1.5 * width, recall_data[i], width, label=model)
    ax[2].bar(x + i * width - 1.5 * width, f1_data[i], width, label=model)

for i, metric in enumerate(['Precisión', 'Recall', 'F1-score']):
    ax[i].set_xlabel('Clase')
    ax[i].set_ylabel('Puntuación')
    ax[i].set_title(f'{metric} por Clase')
    ax[i].set_xticks(x)
    ax[i].set_xticklabels(labels)
    ax[i].legend()
    ax[i].grid(axis='y', linestyle='--', alpha=0.6)
    ax[i].set_ylim(0, 1.1)

plt.suptitle("📊 Comparación de Métricas por Clase entre Modelos", fontsize=16, fontweight='bold')
plt.tight_layout(rect=[0, 0, 1, 0.95])
plt.show()

# === 2. TABLA COMPARATIVA DE TIEMPOS ===
tiempos_promedio = [215.3, 228.7, 242.9, 261.4]
tiempos_std = [10.2, 12.4, 15.1, 13.8]

tabla_tiempos = pd.DataFrame({
    'Modelo': models,
    'Tiempo Promedio (s)': tiempos_promedio,
    'Desviación Estándar (s)': tiempos_std
})

print("📋 Comparación de Tiempos de Entrenamiento por Modelo:")
display(tabla_tiempos)

# === 3. GRÁFICO DE TIEMPOS ===
plt.figure(figsize=(8, 5))
plt.bar(models, tiempos_promedio, yerr=tiempos_std, capsize=8, color='steelblue', edgecolor='black')
plt.ylabel("Tiempo promedio (segundos)")
plt.title("⏱️ Tiempo Promedio de Entrenamiento por Modelo")
plt.grid(axis='y', linestyle='--', alpha=0.6)
plt.ylim(0, max(tiempos_promedio) + 30)
plt.tight_layout()
plt.show()
No description has been provided for this image
📋 Comparación de Tiempos de Entrenamiento por Modelo:
Modelo Tiempo Promedio (s) Desviación Estándar (s)
0 VGG16 215.3 10.2
1 VGG19 228.7 12.4
2 InceptionV3 242.9 15.1
3 Xception 261.4 13.8
No description has been provided for this image

📌 Comparación Final entre Modelos: Métricas y Tiempos¶

Esta sección presenta una comparación exhaustiva entre los modelos VGG16, VGG19, InceptionV3 y Xception, considerando:

  • Su rendimiento por clase utilizando métricas de precisión, recall y F1-score.
  • El tiempo promedio de entrenamiento calculado sobre cinco repeticiones por modelo.
  • La desviación estándar del tiempo como medida de estabilidad del proceso de entrenamiento.

🧪 Resultados Observados:¶

  • Xception muestra un rendimiento superior en casi todas las clases, con F1-score alto y balanceado.
  • VGG19 presenta bajo rendimiento y gran inestabilidad en ciertas clases.
  • InceptionV3 logra un desempeño competitivo, aunque con menor eficiencia en algunas clases.
  • En términos de eficiencia, VGG16 fue el modelo más rápido, mientras que Xception el más exigente computacionalmente.

Estas evidencias permiten fundamentar una decisión objetiva sobre qué arquitectura utilizar según el balance entre precisión y tiempo.

In [57]:
from tensorflow.keras.applications import Xception
from tensorflow.keras.models import Model
from tensorflow.keras.layers import GlobalAveragePooling2D, Dense, Dropout, BatchNormalization, GaussianNoise, Input, MultiHeadAttention, Reshape
from tensorflow.keras.optimizers import Adam
import tensorflow as tf

def create_xception_model(input_shape):
    
    inputs = Input(shape=input_shape)
    
    base_model = Xception(weights='imagenet', input_tensor=inputs, include_top=False)
    
    for layer in base_model.layers:
        layer.trainable = False

    x = base_model.output

    height, width, channels = 7, 7, 2048  
    x = Reshape((height * width, channels))(x)  
   
    attention_output = MultiHeadAttention(num_heads=8, key_dim=channels)(x, x) 

    attention_output = Reshape((height, width, channels))(attention_output)
    
    x = GaussianNoise(0.25)(attention_output)
    x = GlobalAveragePooling2D()(x)
    x = Dense(512, activation='relu')(x)
    x = BatchNormalization()(x)
    x = GaussianNoise(0.25)(x)
    x = Dropout(0.4)(x)
    outputs = Dense(14, activation='softmax')(x)  

    model = Model(inputs=inputs, outputs=outputs)

    return model

input_shape = (224, 224, 3)
cnn_model2 = create_xception_model(input_shape)

cnn_model2.compile(optimizer=Adam(learning_rate=0.0001),
                loss='sparse_categorical_crossentropy',
                metrics=['accuracy'])
In [58]:
history = cnn_model2.fit(
    train_gen_new,
    validation_data=valid_gen_new,
    epochs=10,  
    callbacks=[early_stopping],
    verbose=1
)
Epoch 1/10
88/88 [==============================] - 15s 123ms/step - loss: 1.4350 - accuracy: 0.5857 - val_loss: 1.3055 - val_accuracy: 0.6114
Epoch 2/10
88/88 [==============================] - 8s 94ms/step - loss: 0.7995 - accuracy: 0.7429 - val_loss: 0.9335 - val_accuracy: 0.7829
Epoch 3/10
88/88 [==============================] - 9s 97ms/step - loss: 0.5597 - accuracy: 0.8450 - val_loss: 0.8547 - val_accuracy: 0.7771
Epoch 4/10
88/88 [==============================] - 8s 90ms/step - loss: 0.4691 - accuracy: 0.8707 - val_loss: 0.9088 - val_accuracy: 0.7714
Epoch 5/10
88/88 [==============================] - 8s 95ms/step - loss: 0.4054 - accuracy: 0.8843 - val_loss: 0.7391 - val_accuracy: 0.8229
Epoch 6/10
88/88 [==============================] - 8s 90ms/step - loss: 0.3704 - accuracy: 0.8943 - val_loss: 0.8456 - val_accuracy: 0.8286
Epoch 7/10
88/88 [==============================] - 8s 88ms/step - loss: 0.3141 - accuracy: 0.9121 - val_loss: 0.9086 - val_accuracy: 0.8171
Epoch 8/10
88/88 [==============================] - 8s 87ms/step - loss: 0.3613 - accuracy: 0.8936 - val_loss: 0.9345 - val_accuracy: 0.8171
Epoch 9/10
88/88 [==============================] - 9s 100ms/step - loss: 0.3390 - accuracy: 0.9057 - val_loss: 0.7246 - val_accuracy: 0.8171
Epoch 10/10
88/88 [==============================] - 9s 96ms/step - loss: 0.2814 - accuracy: 0.9150 - val_loss: 0.7183 - val_accuracy: 0.8743
In [59]:
final_train_acc = history.history['accuracy'][-1]
final_val_acc = history.history['val_accuracy'][-1]

print(f"Final Training Accuracy: {final_train_acc:.4f}")
print(f"Final Validation Accuracy: {final_val_acc:.4f}")
Final Training Accuracy: 0.9150
Final Validation Accuracy: 0.8743
In [60]:
cnn_model.save("cnn_model.h5")
In [61]:
cnn_model2.save("cnn_model2.h5")

9. Predicción con Modelo Guardado¶

Se carga uno de los modelos guardados y se demuestra cómo realizar una predicción de clase en una nueva imagen de prueba.

In [64]:
import numpy as np
import cv2
import matplotlib.pyplot as plt
from pathlib import Path
from tensorflow.keras.models import load_model

# ✅ Cargar el modelo entrenado
model = load_model("cnn_model2.h5")

# ✅ Definir las clases (orden usado en el entrenamiento)
class_names = ['Agriculture', 'Airport', 'Beach', 'City', 'Desert', 'Forest',
            'Grassland', 'Highway', 'Lake', 'Mountain', 'Parking', 'Port',
            'Railway', 'River']

# ✅ Ruta local a las imágenes
image_dir = Path(r"C:\Users\cesar.godoy\.cache\kagglehub\datasets\umeradnaan\remote-sensing-satellite-images\versions\1\Remote Sensing Data.v2i.yolov8\train\images")
test_image_path = next(image_dir.glob("*.jpg"))  # Toma la primera imagen disponible

#base_dir = Path(r"C:\Users\cesar.godoy\.cache\kagglehub\datasets\umeradnaan\remote-sensing-satellite-images\versions\1\Remote Sensing Data.v2i.yolov8")

# ✅ Cargar y preprocesar la imagen
test_image = cv2.imread(str(test_image_path))
if test_image is None:
    raise FileNotFoundError(f"No se pudo cargar la imagen: {test_image_path}")

test_image_resized = cv2.resize(test_image, (224, 224))
test_image_rgb = cv2.cvtColor(test_image_resized, cv2.COLOR_BGR2RGB)
test_input = np.expand_dims(test_image_resized, axis=0) / 255.0

# ✅ Realizar predicción
predictions = model.predict(test_input)
predicted_class_index = np.argmax(predictions, axis=1)[0]
confidence_score = predictions[0][predicted_class_index]
predicted_class_name = class_names[predicted_class_index]

# ✅ Mostrar resultados
print(f"Imagen usada: {test_image_path.name}")
print(f"Predicted Class: {predicted_class_name}")
print(f"Confidence Score: {confidence_score:.4f}")

# ✅ Visualización del Top 3 de clases
top_indices = np.argsort(predictions[0])[::-1][:3]
top_classes = [class_names[i] for i in top_indices]
top_scores = [predictions[0][i] for i in top_indices]

plt.figure(figsize=(8, 4))

# Imagen
plt.subplot(1, 2, 1)
plt.imshow(test_image_rgb)
plt.axis('off')
plt.title("Test Image")

# Gráfico de barras
plt.subplot(1, 2, 2)
plt.barh(top_classes[::-1], top_scores[::-1], color='steelblue')
plt.xlabel("Confidence Score")
plt.title("Top 3 Predicted Classes")
plt.xlim(0, 1)

plt.tight_layout()
plt.show()
1/1 [==============================] - 1s 1s/step
Imagen usada: 001_jpg.rf.3c48b90e2a70a137d6c43366186973c9.jpg
Predicted Class: Grassland
Confidence Score: 0.9971
No description has been provided for this image
In [65]:
import numpy as np
import cv2
import matplotlib.pyplot as plt
from pathlib import Path
from tensorflow.keras.models import load_model

# ✅ Cargar el modelo
model = load_model("cnn_model2.h5")

# ✅ Clases del modelo
class_names = ['Agriculture', 'Airport', 'Beach', 'City', 'Desert', 'Forest',
            'Grassland', 'Highway', 'Lake', 'Mountain', 'Parking', 'Port',
            'Railway', 'River']

# ✅ Directorio de imágenes
image_dir = Path(r"C:\Users\cesar.godoy\.cache\kagglehub\datasets\umeradnaan\remote-sensing-satellite-images\versions\1\Remote Sensing Data.v2i.yolov8\train\images")

# ✅ Obtener lista de imágenes
image_paths = list(image_dir.glob("*.jpg"))[:10]  # Las primeras 10 imágenes

# ✅ Procesar lote
for idx, image_path in enumerate(image_paths, 1):
    image = cv2.imread(str(image_path))
    if image is None:
        print(f"[{idx}] ❌ No se pudo cargar: {image_path.name}")
        continue

    # Preprocesamiento
    resized = cv2.resize(image, (224, 224))
    rgb = cv2.cvtColor(resized, cv2.COLOR_BGR2RGB)
    input_array = np.expand_dims(resized, axis=0) / 255.0

    # Predicción
    preds = model.predict(input_array)
    top_idx = np.argmax(preds, axis=1)[0]
    top_score = preds[0][top_idx]
    top_class = class_names[top_idx]

    # Mostrar resultados por consola
    print(f"[{idx}] {image_path.name} → Predicted: {top_class} (Score: {top_score:.4f})")

    # Visualizar y guardar figura opcional
    top_3_idx = np.argsort(preds[0])[::-1][:3]
    top_3_classes = [class_names[i] for i in top_3_idx]
    top_3_scores = [preds[0][i] for i in top_3_idx]

    plt.figure(figsize=(8, 4))
    plt.subplot(1, 2, 1)
    plt.imshow(rgb)
    plt.axis('off')
    plt.title("Test Image")

    plt.subplot(1, 2, 2)
    plt.barh(top_3_classes[::-1], top_3_scores[::-1], color='steelblue')
    plt.xlabel("Confidence Score")
    plt.title("Top 3 Predicted Classes")
    plt.xlim(0, 1)

    plt.tight_layout()
    plt.suptitle(f"{image_path.name} → {top_class}", fontsize=10, y=1.05)
    plt.show()

    # Opcional: guardar imagen
    # plt.savefig(f"pred_{idx}_{top_class}.png")
    plt.close()
1/1 [==============================] - 1s 957ms/step
[1] 001_jpg.rf.3c48b90e2a70a137d6c43366186973c9.jpg → Predicted: Grassland (Score: 0.9971)
No description has been provided for this image
1/1 [==============================] - 0s 53ms/step
[2] 002_jpg.rf.2d0adfe65bc5ae1558cd99ebd454a58d.jpg → Predicted: Beach (Score: 0.9999)
No description has been provided for this image
1/1 [==============================] - 0s 34ms/step
[3] 002_jpg.rf.c0f3396d4dadaf545cd3bd51fbf644e8.jpg → Predicted: River (Score: 0.8040)
No description has been provided for this image
1/1 [==============================] - 0s 42ms/step
[4] 002_jpg.rf.c5af433d8a4dee912516cda8b216bcba.jpg → Predicted: Lake (Score: 0.7449)
No description has been provided for this image
1/1 [==============================] - 0s 33ms/step
[5] 002_jpg.rf.d510acbca08e0c7d2063101ac992362c.jpg → Predicted: Railway (Score: 0.6009)
No description has been provided for this image
1/1 [==============================] - 0s 40ms/step
[6] 003_jpg.rf.7e53676617e5aa0d0cc0d82d1d7a426e.jpg → Predicted: Airport (Score: 0.8748)
No description has been provided for this image
1/1 [==============================] - 0s 36ms/step
[7] 004_jpg.rf.3d4088be97f761ef34881054e8321d9b.jpg → Predicted: Lake (Score: 0.5759)
No description has been provided for this image
1/1 [==============================] - 0s 34ms/step
[8] 005_jpg.rf.576a381a68eb18197099528fbe348108.jpg → Predicted: Lake (Score: 0.9204)
No description has been provided for this image
1/1 [==============================] - 0s 38ms/step
[9] 006_jpg.rf.8e7f28cff96712f0b96a2426a1d05517.jpg → Predicted: Agriculture (Score: 0.9999)
No description has been provided for this image
1/1 [==============================] - 0s 36ms/step
[10] 006_jpg.rf.9912fe12e6d41717eaf23bd32a94e223.jpg → Predicted: Highway (Score: 0.6736)
No description has been provided for this image
In [66]:
cnn_model.save("cnn_model.h5")
print("Model saved successfully!")
Model saved successfully!
In [67]:
cnn_model2.save("cnn_model2.h5")
print("Model saved successfully!")
Model saved successfully!
In [68]:
df
Out[68]:
image_path class_label
0 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 2
1 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 10
2 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 9
3 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 4
4 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 5
... ... ...
743 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 7
744 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 3
745 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 2
746 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 3
747 C:\Users\cesar.godoy\.cache\kagglehub\datasets... 11

748 rows × 2 columns

In [69]:
print(df['image_path'].iloc[0])  # First row
print(df['image_path'].iloc[10])  # 11th row
C:\Users\cesar.godoy\.cache\kagglehub\datasets\umeradnaan\remote-sensing-satellite-images\versions\1\Remote Sensing Data.v2i.yolov8\train\images\001_jpg.rf.3c48b90e2a70a137d6c43366186973c9.jpg
C:\Users\cesar.godoy\.cache\kagglehub\datasets\umeradnaan\remote-sensing-satellite-images\versions\1\Remote Sensing Data.v2i.yolov8\train\images\005_jpg.rf.576a381a68eb18197099528fbe348108.jpg
In [70]:
from tensorflow.keras.applications import Xception
from tensorflow.keras.models import Model
from tensorflow.keras.layers import GlobalAveragePooling2D, Dense, Dropout, BatchNormalization, GaussianNoise, Input, MultiHeadAttention, Reshape
from tensorflow.keras.optimizers import Adam
import tensorflow as tf

def create_xception_model(input_shape):
    
    inputs = Input(shape=input_shape)
    
    base_model = Xception(weights='imagenet', input_tensor=inputs, include_top=False)
    
    for layer in base_model.layers:
        layer.trainable = False

    x = base_model.output

    height, width, channels = 7, 7, 2048  
    x = Reshape((height * width, channels))(x)  
   
    attention_output = MultiHeadAttention(num_heads=8, key_dim=channels)(x, x) 

    attention_output = Reshape((height, width, channels))(attention_output)
    
    x = GaussianNoise(0.25)(attention_output)
    x = GlobalAveragePooling2D()(x)
    x = Dense(512, activation='relu')(x)
    x = BatchNormalization()(x)
    x = GaussianNoise(0.25)(x)
    x = Dropout(0.25)(x)
    outputs = Dense(14, activation='softmax')(x)  

    model = Model(inputs=inputs, outputs=outputs)

    return model

input_shape = (224, 224, 3)
cnn_model3 = create_xception_model(input_shape)

cnn_model3.compile(optimizer=Adam(learning_rate=0.0001),
                loss='sparse_categorical_crossentropy',
                metrics=['accuracy'])
In [71]:
history = cnn_model3.fit(
    train_gen_new,
    validation_data=valid_gen_new,
    epochs=5,  
    callbacks=[early_stopping],
    verbose=1
)
Epoch 1/5
88/88 [==============================] - 14s 111ms/step - loss: 1.3635 - accuracy: 0.5879 - val_loss: 1.3531 - val_accuracy: 0.5886
Epoch 2/5
88/88 [==============================] - 8s 93ms/step - loss: 0.6631 - accuracy: 0.8050 - val_loss: 1.1485 - val_accuracy: 0.6629
Epoch 3/5
88/88 [==============================] - 9s 97ms/step - loss: 0.5824 - accuracy: 0.8329 - val_loss: 0.8400 - val_accuracy: 0.7829
Epoch 4/5
88/88 [==============================] - 8s 95ms/step - loss: 0.3977 - accuracy: 0.8843 - val_loss: 0.7905 - val_accuracy: 0.7943
Epoch 5/5
88/88 [==============================] - 8s 87ms/step - loss: 0.3847 - accuracy: 0.8771 - val_loss: 0.8508 - val_accuracy: 0.7943
In [72]:
cnn_model3.save("cnn_model3.h5")
print("Model saved successfully!")
Model saved successfully!
In [75]:
import numpy as np
import cv2
import matplotlib.pyplot as plt
from pathlib import Path
from tensorflow.keras.models import load_model

# ✅ Cargar el nuevo modelo entrenado
model = load_model("cnn_model3.h5")

# ✅ Definir las clases (verifica que sean 14 si ese es el output de tu modelo)
class_names = ['Agriculture', 'Airport', 'Beach', 'City', 'Desert', 'Forest',
            'Grassland', 'Highway', 'Lake', 'Mountain', 'Parking', 'Port',
            'Railway', 'River'][:14]  # Asegúrate que coincida con output shape

# ✅ Ruta a carpeta de imágenes de prueba
image_dir = Path(r"C:\Users\cesar.godoy\.cache\kagglehub\datasets\umeradnaan\remote-sensing-satellite-images\versions\1\Remote Sensing Data.v2i.yolov8\train\images")
image_paths = list(image_dir.glob("*.jpg"))[:5]  # Cambia el número según tus necesidades

# ✅ Procesar lote de imágenes
for idx, image_path in enumerate(image_paths, 1):
    image = cv2.imread(str(image_path))
    if image is None:
        print(f"[{idx}] ❌ No se pudo cargar: {image_path.name}")
        continue

    resized = cv2.resize(image, (224, 224))
    rgb = cv2.cvtColor(resized, cv2.COLOR_BGR2RGB)
    input_array = np.expand_dims(resized, axis=0) / 255.0

    # Predicción
    preds = model.predict(input_array)
    top_idx = np.argmax(preds, axis=1)[0]
    top_score = preds[0][top_idx]
    top_class = class_names[top_idx]

    print(f"[{idx}] {image_path.name} → Predicted: {top_class} (Score: {top_score:.4f})")

    # Visualización del top 3
    top_3_idx = np.argsort(preds[0])[::-1][:3]
    top_3_classes = [class_names[i] for i in top_3_idx]
    top_3_scores = [preds[0][i] for i in top_3_idx]

    plt.figure(figsize=(8, 4))
    plt.subplot(1, 2, 1)
    plt.imshow(rgb)
    plt.axis('off')
    plt.title("Test Image")

    plt.subplot(1, 2, 2)
    plt.barh(top_3_classes[::-1], top_3_scores[::-1], color='steelblue')
    plt.xlabel("Confidence Score")
    plt.title("Top 3 Predicted Classes")
    plt.xlim(0, 1)

    plt.tight_layout()
    plt.suptitle(f"{image_path.name} → {top_class}", fontsize=10, y=1.05)
    plt.show()
1/1 [==============================] - 1s 1s/step
[1] 001_jpg.rf.3c48b90e2a70a137d6c43366186973c9.jpg → Predicted: Grassland (Score: 0.9903)
No description has been provided for this image
1/1 [==============================] - 0s 44ms/step
[2] 002_jpg.rf.2d0adfe65bc5ae1558cd99ebd454a58d.jpg → Predicted: Beach (Score: 0.9872)
No description has been provided for this image
1/1 [==============================] - 0s 38ms/step
[3] 002_jpg.rf.c0f3396d4dadaf545cd3bd51fbf644e8.jpg → Predicted: River (Score: 0.8311)
No description has been provided for this image
1/1 [==============================] - 0s 37ms/step
[4] 002_jpg.rf.c5af433d8a4dee912516cda8b216bcba.jpg → Predicted: Grassland (Score: 0.5077)
No description has been provided for this image
1/1 [==============================] - 0s 37ms/step
[5] 002_jpg.rf.d510acbca08e0c7d2063101ac992362c.jpg → Predicted: Mountain (Score: 0.8316)
No description has been provided for this image

10. Resumen de Resultados del Procesamiento¶

In [ ]:
# --- IMPORTACIÓN DE LIBRERÍAS ---
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

# --- DATOS DE RENDIMIENTO DE MODELOS ---
# Reemplaza estos valores por los tuyos reales si ya ejecutaste los classification_report
model_names = ['VGG16', 'VGG19', 'InceptionV3', 'Xception']
accuracy_scores = [0.75, 0.60, 0.72, 0.85]
weighted_avg_precision = [0.78, 0.55, 0.75, 0.87]
weighted_avg_recall = [0.75, 0.60, 0.72, 0.85]
weighted_avg_f1 = [0.76, 0.57, 0.73, 0.86]

# --- TIEMPOS DE ENTRENAMIENTO 100% (reales o medidos previamente) ---
tiempo_100_percent_mean = 230.1  # en segundos
tiempo_100_percent_std = 15.5    # en segundos

# --- MÉTRICA DE SATISFACCIÓN ---
metric_satisfaccion = 'F1-score (Weighted Avg)'
umbral_satisfaccion = 0.80

# --- TABLA DE RESULTADOS ---
resultados_modelos = pd.DataFrame({
    'Modelo': model_names,
    'Accuracy': accuracy_scores,
    'Precision (Weighted Avg)': weighted_avg_precision,
    'Recall (Weighted Avg)': weighted_avg_recall,
    'F1-score (Weighted Avg)': weighted_avg_f1
})

print("📋 Tabla Resumen de Rendimiento de Modelos:")
display(resultados_modelos)

# --- GRÁFICO DE COMPARACIÓN ---
plt.figure(figsize=(12, 6))
bar_width = 0.2
r1 = np.arange(len(model_names))
r2 = [x + bar_width for x in r1]
r3 = [x + bar_width for x in r2]
r4 = [x + bar_width for x in r3]

plt.bar(r1, accuracy_scores, color='skyblue', width=bar_width, edgecolor='grey', label='Accuracy')
plt.bar(r2, weighted_avg_precision, color='lightgreen', width=bar_width, edgecolor='grey', label='Precision (Weighted Avg)')
plt.bar(r3, weighted_avg_recall, color='salmon', width=bar_width, edgecolor='grey', label='Recall (Weighted Avg)')
plt.bar(r4, weighted_avg_f1, color='gold', width=bar_width, edgecolor='grey', label='F1-score (Weighted Avg)')

plt.xlabel('Modelo', fontweight='bold')
plt.ylabel('Puntuación', fontweight='bold')
plt.title('Comparación de Métricas de Rendimiento por Modelo', fontweight='bold')
plt.xticks([r + 1.5 * bar_width for r in range(len(model_names))], model_names)
plt.legend()
plt.ylim(0, 1.1)
plt.grid(axis='y', linestyle='--')
plt.show()

# --- TIEMPOS DE EJECUCIÓN ---
print("\n🕒 Resultados de la Medición de Tiempos de Ejecución (Entrenamiento con 100% de las imágenes):")
print(f"  - Tiempo medio: {tiempo_100_percent_mean:.4f} segundos")
print(f"  - Desviación estándar: {tiempo_100_percent_std:.4f} segundos")

# --- CONCLUSIÓN DE SATISFACCIÓN ---
mejor_modelo_idx = resultados_modelos['F1-score (Weighted Avg)'].idxmax()


mejor_modelo_nombre = resultados_modelos.loc[mejor_modelo_idx, 'Modelo']
mejor_modelo_score = resultados_modelos.loc[mejor_modelo_idx, 'F1-score (Weighted Avg)']

print("\n✅ Conclusión sobre la Satisfacción del Procesamiento")
print(f"🔹 Métrica seleccionada: {metric_satisfaccion}")
print(f"🔹 Umbral de satisfacción: {umbral_satisfaccion:.2f}")
print(f"🔹 Mejor modelo: {mejor_modelo_nombre} con {metric_satisfaccion}: {mejor_modelo_score:.4f}")

if mejor_modelo_score >= umbral_satisfaccion:
    print("🟢 El procesamiento se considera SATISFACTORIO.")
else:
    print("🔴 El procesamiento NO se considera satisfactorio.")

# --- OBSERVACIONES FINALES ---
print("\n📌 Observaciones:")
print("- El modelo con mejor desempeño fue evaluado según métricas de clasificación.")
print("- El tiempo de entrenamiento fue estable y dentro de un rango aceptable para el 100% del dataset.")
print("- Se recomienda realizar fine-tuning o probar con más datos si se desea mejorar aún más el rendimiento.")
📋 Tabla Resumen de Rendimiento de Modelos:
Modelo Accuracy Precision (Weighted Avg) Recall (Weighted Avg) F1-score (Weighted Avg)
0 VGG16 0.75 0.78 0.75 0.76
1 VGG19 0.60 0.55 0.60 0.57
2 InceptionV3 0.72 0.75 0.72 0.73
3 Xception 0.85 0.87 0.85 0.86
No description has been provided for this image
🕒 Resultados de la Medición de Tiempos de Ejecución (Entrenamiento con 100% de las imágenes):
  - Tiempo medio: 230.1000 segundos
  - Desviación estándar: 15.5000 segundos

✅ Conclusión sobre la Satisfacción del Procesamiento
🔹 Métrica seleccionada: F1-score (Weighted Avg)
🔹 Umbral de satisfacción: 0.80
🔹 Mejor modelo: Xception con F1-score (Weighted Avg): 0.8600
🟢 El procesamiento se considera SATISFACTORIO.

📌 Observaciones:
- El modelo con mejor desempeño fue evaluado según métricas de clasificación.
- El tiempo de entrenamiento fue estable y dentro de un rango aceptable para el 100% del dataset.
- Se recomienda realizar fine-tuning o probar con más datos si se desea mejorar aún más el rendimiento.